Skip to content

VableArrayStore - #1152

Merged
youknowone merged 3 commits into
mainfrom
wasm-jit
Aug 11, 2026
Merged

VableArrayStore#1152
youknowone merged 3 commits into
mainfrom
wasm-jit

Conversation

@youknowone

@youknowone youknowone commented Aug 11, 2026

Copy link
Copy Markdown
Owner

Summary by CodeRabbit

  • Bug Fixes

    • Improved snapshot handling for virtualizable field and array stores, including pre-store state capture and multiple guards from a single operation.
    • Added explicit handling for out-of-range array stores.
    • Improved guard resume-position tracking for complex tracing scenarios.
  • Tests

    • Expanded coverage for virtualizable stores, live-null values, repeated stores, and guard snapshot behavior.
  • Documentation

    • Clarified platform-specific behavior for exact-string iterator replay.

@coderabbitai

coderabbitai Bot commented Aug 11, 2026

Copy link
Copy Markdown

Review Change Stack

Walkthrough

The change adds structured virtualizable store results, preserves overwritten shadow entries during snapshot capture, and supports reverse-indexed guard resume stamping. Runtime handlers and tests now use VableArrayStore variants.

Changes

Virtualizable snapshot and guard handling

Layer / File(s) Summary
Store result and guard contracts
majit/majit-metainterp/src/trace_ctx.rs, majit/majit-metainterp/src/recorder.rs, majit/majit-metainterp/src/history.rs, majit/majit-metainterp/src/lib.rs
Store operations return structured results with overwritten-entry metadata. Public APIs support reverse-indexed guard resume stamping.
Snapshot publication and guard targeting
majit/majit-metainterp/src/pyjitpl/dispatch.rs
Promote snapshots restore pre-store virtualizable state, stamp every emitted guard, and distinguish out-of-vable array stores.
Runtime adapters and validation
majit/majit-metainterp/src/pyjitpl.rs, pyre/pyre-jit-trace/src/jitcode_dispatch/tests.rs, pyre/pyre-jit-trace/src/jitcode_dispatch/residual_call.rs
Array-store handlers and tests use VableArrayStore variants. The wasm-specific replay rationale is expanded without executable changes.

Estimated code review effort: 4 (Complex) | ~45 minutes

Sequence Diagram(s)

sequenceDiagram
  participant VableStore
  participant TraceCtx
  participant SnapshotCapture
  participant Trace
  VableStore->>TraceCtx: return VableArrayStore
  TraceCtx->>SnapshotCapture: provide overwritten virtualizable entry
  SnapshotCapture->>Trace: stamp emitted guards from the end
  Trace->>SnapshotCapture: publish resume snapshots
Loading

Possibly related PRs

  • youknowone/pyre#1140: Shares guard resume-position stamping and virtualizable snapshot publication changes.
  • youknowone/pyre#1143: Extends vable-promote guard snapshot stamping in pyjitpl/dispatch.rs.
  • youknowone/pyre#834: Shares virtualizable tracing changes across TraceCtx, pyjitpl, and dispatch logic.

Poem

I’m a rabbit guarding each stored trace,
Pre-store shadows wait in place.
Guards stamp backward, neat and bright,
Arrays report their true write type.
Tests hop through every case—
Snapshot paths now leave no trace misplaced.

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Docstring Coverage ✅ Passed Docstring coverage is 100.00% which is sufficient. The required threshold is 80.00%.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
Title check ✅ Passed The title names VableArrayStore, a central API added and used by the pull request, but it does not describe the related guard snapshot changes.
✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch wasm-jit

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

…every guard one opcode emits

`capture_vable_promote_guard` ran after the whole `vable_*` call returned.

`publish_last_guard_resume_snapshot` clones `ctx.virtualizable_boxes`, and by
then the standard leg of `vable_setfield` / `vable_setarrayitem_indexed` has
already written the slot -- so the promote guard's resume data carried the very
write its own resume pc re-executes. `_opimpl_setfield_vable` /
`_opimpl_setarrayitem_vable` (pyjitpl.py:1188 / :1236) reach
`virtualizable_boxes[index] = valuebox` only after the promotes have captured.
Both setters now report the slot they overwrote (`VableEntryWrite`) and the
capture puts it back for its duration through `swap_virtualizable_entry`, which
leaves `virtualizable_live_null_slots` alone where the store clears it and the
`live_null_push` arm sets it right after.

One vable array access can also emit two guards: the `isstandard` PTR_EQ in
`_nonstandard_virtualizable` (:1135-1138) and then the index in
`_get_arrayitem_vable_index` (:1201-1216). Only the last was stamped, so the
first kept the `UNSTAMPED_JITCODE_INDEX` frame `record_guard_with_snapshot`
mints. `set_guard_op_resume_position_from_end` walks back over every guard the
call added; `set_last_guard_op_resume_position` is now its `from_end == 0` case.

Two unit tests, each checked to fail with its half of the change reverted: the
setarrayitem promote guard's snapshot holds the pre-store Box and not the value
written, and both guards of a two-promote opcode point at a stamped frame.

Assisted-by: Claude
The comment on `native_exact_str_replay` cited three compile aborts. Dropping
the gate and rebuilding the guest reads `abrt_bad_loop=1` on
`str_search_index_bounds`, with `bridges_compiled` 7 -> 5 and `guard_failures`
2096 -> 7098 against 1897 for the native backends, so the boundary stays.

Also records what the boundary itself costs on that bench: the hazardous-callee
arm of `fbw_abort_nested_unjournaled_residual` declines once, and the guard it
leaves unbridged re-fires 199 more times, one `trace_eagerness` cycle later.
Forcing the gate on for pyre-dynasm reproduces the recorded wasm row exactly
(5 loops / 7 bridges / 1 abort / 2096 guard failures against 5 / 7 / 0 / 1897).

Assisted-by: Claude
…he four callers the enum broke

`capture_vable_promote_guard` walked `0..minted`, newest guard first.
`publish_last_guard_resume_snapshot` is not side-effect free -- it leaves the
root frame's in-flight result slot cleared (`root_inflight_int_result`) -- so
the order decides what the earlier guard's snapshot sees. `generate_guard`
(pyjitpl.py:2582-2603) captures each guard synchronously as it is emitted, so
the loop runs oldest-first.

The `bool -> VableArrayStore` return also broke four `assert!` call sites in
`pyre-jit-trace`'s test module, which a release binary build and
`cargo test -p majit-metainterp` both compile past; `cargo check --workspace
--all-targets` is what reports them. `VableArrayStore` is re-exported from the
crate root so the external test can name it.

The doc comment claimed pyre's two snapshots match what upstream would produce.
They do not: `MetaInterp.replace_box` walks the framestack
(`frame.replace_active_box_in_frame`) and `TraceCtx::replace_box` does not, so
upstream's second capture would see the standard box. Stated as the pre-existing
gap it is.

Assisted-by: Claude
@youknowone
youknowone marked this pull request as ready for review August 11, 2026 01:29
@github-actions

Copy link
Copy Markdown

🤖 Codex parity review

Static analysis of this diff vs the local RPython/PyPy sources (commit 89192db).
Updated: 2026-08-11T01:33:54.858Z

Files in the reviewed diff
majit/majit-metainterp/src/history.rs
majit/majit-metainterp/src/lib.rs
majit/majit-metainterp/src/pyjitpl.rs
majit/majit-metainterp/src/pyjitpl/dispatch.rs
majit/majit-metainterp/src/recorder.rs
majit/majit-metainterp/src/trace_ctx.rs
pyre/pyre-jit-trace/src/jitcode_dispatch/residual_call.rs
pyre/pyre-jit-trace/src/jitcode_dispatch/tests.rs

1. Regressions to PyPy parity introduced by this patch

None.

2. Other mismatches introduced by this patch

None.

3. Pre-existing mismatches (already present before this patch)

  • majit/majit-metainterp/src/history.rs:2625 ↔ rpython/jit/metainterp/pyjitpl.py:3523: state-field tracing calls TraceCtx::replace_box, which updates side tables but not live MIFrames; PyPy’s MetaInterp.replace_box first rewrites every frame. Thus a later guard snapshot can retain the pre-promotion frame box.

  • majit/majit-metainterp/src/trace_ctx.rs:4488 ↔ rpython/jit/metainterp/pyjitpl.py:1205: Pyre turns a negative/out-of-range virtualizable-array index into None and falls back/aborts; PyPy promotes the index and asserts 0 <= index < length.

  • pyre/pyre-jit-trace/src/jitcode_dispatch/residual_call.rs:2800 ↔ pypy/objspace/std/unicodeobject.py:151: on wasm, native_exact_str_replay declines replay of exact-string iteration, whereas PyPy’s exact-unicode iterator directly creates W_FastUnicodeIterObject.

  • pyre/pyre-jit-trace/src/jitcode_dispatch/residual_call.rs:2811 ↔ pypy/objspace/std/unicodeobject.py:155: the same wasm gate declines exact-string ord replay, whereas PyPy reads the immutable Unicode payload directly.

4. Structural adaptations

  • majit/majit-metainterp/src/trace_ctx.rs:708 ↔ rpython/jit/metainterp/pyjitpl.py:1189: VableEntryWrite/VableArrayStore preserve the overwritten virtualizable shadow while Rust’s dispatcher captures guard snapshots after the fused trace-context operation. PyPy captures inside generate_guard before the setter assignment; the save/restore reproduces that ordering.

  • majit/majit-metainterp/src/pyjitpl/dispatch.rs:1527 ↔ rpython/jit/metainterp/pyjitpl.py:2582: Rust stamps already-recorded guards by reverse guard ordinal because snapshot construction is owned by the dispatcher; PyPy records and captures each guard atomically. The patch’s emission-order loop reproduces PyPy’s per-guard capture order.

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 89192dbce4

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

}
self.synchronize_virtualizable();
true
VableArrayStore::Stored(overwritten)

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Propagate the pre-store slot through the production walker

When a standard virtualizable store emits a promote guard, the production pyre-jit-trace handlers still discard this Stored(overwritten) value (vable_ops.rs:813) and call walker_capture_inline_nonstandard_vable_guard only after the shadow was updated (vable_ops.rs:839); vable_setfield is discarded similarly at lines 334-347. Thus, on a dynamic-index guard failure, resume data still restores the traced write into its old slot before re-executing the setter at the runtime index, potentially modifying two slots. The rollback token must be threaded into the production snapshot capture, not only the majit-metainterp test dispatcher.

AGENTS.md reference: AGENTS.md:L14-L19

Useful? React with 👍 / 👎.

/// data inside each `implement_guard_value`, so both are stamped; a caller
/// that only reaches the guards after the helper returns walks back over
/// them with this.
pub fn set_guard_op_resume_position_from_end(&mut self, from_end: usize, snapshot_id: i32) {

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Use multi-guard stamping in the production resume path

For a pointer-equal but non-identical virtualizable with a dynamic array index, one access emits both the isstandard and index guards, but the production helper walker_capture_inline_nonstandard_vable_guard still calls only the capture_snapshot_for_last_guard_* variants (resume_snapshot.rs:166-173,212-219,246-253). Consequently only the index guard is restamped and the earlier guard retains its UNSTAMPED_JITCODE_INDEX placeholder, so a retained/failing identity guard cannot be decoded correctly. Wire this new indexed guard-stamping API into that production helper and capture every guard emitted by the opcode.

AGENTS.md reference: AGENTS.md:L231-L233

Useful? React with 👍 / 👎.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
majit/majit-metainterp/src/pyjitpl.rs (1)

5089-5108: 🎯 Functional Correctness | 🟠 Major | ⚡ Quick win

Handle VableArrayStore::OutOfVable instead of asserting Stored.

vable_setarrayitem_indexed returns OutOfVable for an out-of-range virtualizable index. This is a valid trace-abort outcome, not a missing-slot invariant violation. The assertions in all three opimpl_setarrayitem_vable_* methods can panic before the caller handles the abort.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@majit/majit-metainterp/src/pyjitpl.rs` around lines 5089 - 5108, Update all
three opimpl_setarrayitem_vable_* methods to handle VableArrayStore::OutOfVable
as the valid trace-abort result from vable_setarrayitem_indexed, rather than
asserting Stored. Preserve assertion or invariant handling only for genuinely
unexpected results, and allow the existing caller abort path to process
out-of-range indices.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@pyre/pyre-jit-trace/src/jitcode_dispatch/tests.rs`:
- Around line 604-617: In the repeated-store assertions around
vable_setarrayitem_indexed, tighten the VableArrayStore::Stored pattern from
Stored(_) to Stored(Some(_)) at both referenced assertion sites. Preserve the
existing setup and verify that each overwrite reports a previous shadow entry
rather than accepting Stored(None).

---

Outside diff comments:
In `@majit/majit-metainterp/src/pyjitpl.rs`:
- Around line 5089-5108: Update all three opimpl_setarrayitem_vable_* methods to
handle VableArrayStore::OutOfVable as the valid trace-abort result from
vable_setarrayitem_indexed, rather than asserting Stored. Preserve assertion or
invariant handling only for genuinely unexpected results, and allow the existing
caller abort path to process out-of-range indices.
🪄 Autofix

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: ASSERTIVE

Plan: Pro Plus

Run ID: aab67c93-a175-47cc-95b1-0dfa51f1dc28

📥 Commits

Reviewing files that changed from the base of the PR and between d7a69fb and 89192db.

📒 Files selected for processing (8)
  • majit/majit-metainterp/src/history.rs
  • majit/majit-metainterp/src/lib.rs
  • majit/majit-metainterp/src/pyjitpl.rs
  • majit/majit-metainterp/src/pyjitpl/dispatch.rs
  • majit/majit-metainterp/src/recorder.rs
  • majit/majit-metainterp/src/trace_ctx.rs
  • pyre/pyre-jit-trace/src/jitcode_dispatch/residual_call.rs
  • pyre/pyre-jit-trace/src/jitcode_dispatch/tests.rs

Comment on lines +604 to 617
assert!(matches!(
tc.vable_setarrayitem_indexed(
0,
vable,
index0,
0,
fdescr.clone(),
adescr.clone(),
const_null,
null,
false,
),
VableArrayStore::Stored(_)
));

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🗄️ Data Integrity & Integration | 🟡 Minor | ⚡ Quick win

Assert the overwritten payload.

VableArrayStore::Stored(_) also matches VableArrayStore::Stored(None). Lines 604-617 and 638-641 run after earlier writes to flat_base, so these assertions do not prove that VableEntryWrite::of returned the previous shadow entry. A regression that drops that entry can still pass this test. Require VableArrayStore::Stored(Some(_)) for these repeated stores.

Proposed assertion tightening
-        VableArrayStore::Stored(_)
+        VableArrayStore::Stored(Some(_))

Also applies to: 638-641

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@pyre/pyre-jit-trace/src/jitcode_dispatch/tests.rs` around lines 604 - 617, In
the repeated-store assertions around vable_setarrayitem_indexed, tighten the
VableArrayStore::Stored pattern from Stored(_) to Stored(Some(_)) at both
referenced assertion sites. Preserve the existing setup and verify that each
overwrite reports a previous shadow entry rather than accepting Stored(None).

@youknowone
youknowone merged commit 295d5a6 into main Aug 11, 2026
18 checks passed
@youknowone
youknowone deleted the wasm-jit branch August 11, 2026 04:58
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant